﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    public class Document
    {
        public static List<string> allPermutations = new List<string>();// keeps all possible nGram
        public static HashSet<string> repeatPermutation = new HashSet<string>();// for checking if permutation is exist allready 
        public static string[] allWordsInTextInArray; // every doc holds global array of its words which are been use for taking the words from the right index
        
        public string m_Name { get; set; } // doc name
        public string m_DocumentContent { get; set; } // doc content text
        public int m_nGrams { get; set; } // # nGrams in a doc
        public int m_mostCommonNGram { get; set; } // the nGram most commen in the doc
        public Dictionary<int, int> m_allHashedNGrams { get; set; }//key- hashed ngram, value- num of appearence in doc
        public Dictionary<int, string> m_allNGramsSentences { get; set; } //key-hashed ngram ,value- string ngram
        public Dictionary<int, List<double>> m_fingerPrints { get; set; }

        public Document(string fName, string docText)
        {
            this.m_Name = fName;
            m_allHashedNGrams = new Dictionary<int, int>();
            m_allNGramsSentences = new Dictionary<int, string>();
            m_nGrams = 0;
            m_mostCommonNGram = 0;
            this.m_DocumentContent = docText;
            allWordsInTextInArray = this.m_DocumentContent.Split(' ');
            m_fingerPrints = new Dictionary<int, List<double>>();
        }


        /// <summary>
        /// main function wich handles all the nGram Recursion
        /// </summary>
        /// <param name="text"></param>
        /// <param name="k"></param>
        /// <param name="n"></param>
        /// <param name="toSort"></param>
        public void MakeNGramsFromText(int k, int n, bool toSort)
        {
            string nGramString;
            int[] forRec = new int[n + k];
            for (int i = 0; i < allWordsInTextInArray.Length - (n + k) + 1; i++) // all the words in the doc.
            {
                for (int j = i, m = 0; j < i + n + k; j++, m++) // assemble the number which we want to get all its permutations.
                    forRec[m] = j;

                allPermutations = CreatePermutations(forRec, n);
                foreach (string CurrentNGram in allPermutations)
                {
                    string[] currPermArray = CurrentNGram.Split(',');
                    nGramString = "";
                    nGramString = GetFixedStringFromPermutation(currPermArray, toSort); // get string of indexes of array and make it one string

                    int hashedKey = nGramString.GetHashCode(); // make hash for current part of the text                

                    if (!(m_allHashedNGrams.ContainsKey(hashedKey)))
                    {
                        m_allHashedNGrams.Add(hashedKey, 1); // insert to dic with hash and number of occurences in the doc
                        m_allNGramsSentences.Add(hashedKey, nGramString); // insert to dic with hash and the suitable string
                    }
                    else
                        m_allHashedNGrams[hashedKey]++; // increment the occurences of the specific hash

                    m_nGrams++; // count all the n grams in the doc.
                } // foreach
                allPermutations.Clear();
            }// for
            repeatPermutation.Clear();
            m_mostCommonNGram = GetMaxOccurencesInDoc(n, k);
        }

        /// <summary>
        /// for every document cut the text by given length and skip size and creates dic of all the finger prints.
        /// </summary>
        /// <param name="fp_Size"></param>
        /// <param name="skipSize"></param>
        /// <param name="toSort"></param>
        public void CreateFingerPrints(int fp_Size, int skipSize, bool toSort)
        {
            if (skipSize == 0)
            {
                Console.WriteLine("You can't choose skip_size with size ZERO because it doesn't have any meaning.");
                return;
            }
            List<string> fp_forPresentation = new List<string>(); // TODO delete this list it is just for presentation
            int docLengt = allWordsInTextInArray.Length;
            string fp_Word; // the words which assembly the fingerprint.
            double relatedLocation = 0;

            for (int location = 0; location < docLengt-fp_Size; location += skipSize)
            {
                fp_Word = "";

                for (int word = location; word < location + fp_Size; word++)
                    fp_Word += allWordsInTextInArray[word] + " "; // assembly the fingerprint.

                fp_Word = fp_Word.TrimEnd(' ');

                if (toSort)
                    fp_Word = SortString(fp_Word); // if sorting the finger print is required.

                int fp_Hash = fp_Word.GetHashCode(); // create hash for the fingerprint.
                
                fp_forPresentation.Add(fp_Word);
                
                if (!m_fingerPrints.ContainsKey(fp_Hash))
                    m_fingerPrints.Add(fp_Hash, new List<double>());

                relatedLocation = (double)location / (double)docLengt;
                relatedLocation = Math.Round(relatedLocation, 3);
                m_fingerPrints[fp_Hash].Add(relatedLocation);
               
            }
            if (this.m_Name == "Global_4_Presentaion.txt")
            {
                using (System.IO.StreamWriter file = new System.IO.StreamWriter(@"Global_4_Presentaion_FingerPrints.txt"))
                foreach (string s in fp_forPresentation)
                {
                    file.WriteLine(s);
                }
            }
        }

        /// <summary>
        /// same function but without sorting the string. returns the nGram as is.
        /// </summary>
        /// <param name="permutation"></param>
        /// <param name="allWords"></param>
        /// <param name="toSort"></param>
        /// <returns></returns>
        private static string GetFixedStringFromPermutation(string[] permutation, bool toSort)
        {
            string res = "";
            for (int i = 1; i < permutation.Length; i++)
            {
                int index = int.Parse(permutation[i]);
                res += allWordsInTextInArray[index] + " ";
            }
            res = res.TrimEnd(' '); // clean the end of the sentence from the char 'space'
            res = res.ToLower();

            if (toSort)
                res = SortString(res);
            return res;
        }

        /// <summary>
        /// sort every n grams if sorted k-skip n-grams is needed.
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        private static string SortString(string s)
        {
            string sortedString = "";
            string[] wordsToSort = s.Split(' ');
            Array.Sort(wordsToSort);

            foreach (string word in wordsToSort)
            {
                sortedString += word + " "; // arrange every word to be sorted
            }

            sortedString = sortedString.TrimEnd(' ');

            return sortedString;
        }

        /// <summary>
        /// recursive func that assemble all the possible permutations under the condition of n.
        /// </summary>
        /// <param name="s"></param>
        /// <param name="wantedLength"></param>
        /// <returns></returns>
        private static List<string> CreatePermutations(int[] s, int wantedLength)
        {
            GetPermutations("", s, s.Length - wantedLength, allPermutations);
            return allPermutations;
        }

        /// <summary>
        /// generate all permutations
        /// </summary>
        /// <param name="prefix"></param>
        /// <param name="s"></param>
        /// <param name="sizeOfSection"></param>
        /// <param name="allPermutations"></param>
        public static void GetPermutations(string prefix, int[] s, int sizeOfSection, List<string> allPermutations)
        {
            int size = s.Length;
            if (size == sizeOfSection)
            {
                if (!repeatPermutation.Contains(prefix))
                {
                    allPermutations.Add(prefix);
                    repeatPermutation.Add(prefix);
                }
                else
                    return;
            }
            else
            {
                for (int i = 0; i < size; i++)
                {
                    if (prefix.Length == 0 || Convert.ToInt32(prefix.Split(',')[prefix.Split(',').Length - 1]) < s[i])
                        GetPermutations(prefix + "," + s[i], appendArrays(subArray(s, 0, i - 1), subArray(s, i + 1, s.Length - 1)), sizeOfSection, allPermutations);
                    else
                        continue;
                }
            }
        }

        /// <summary>
        /// fixed every sub array for the next recursion step
        /// </summary>
        /// <param name="arr"></param>
        /// <param name="startIndex"></param>
        /// <param name="endIndex"></param>
        /// <returns></returns>
        public static int[] subArray(int[] arr, int startIndex, int endIndex)
        {
            int[] ans = new int[endIndex - startIndex + 1];

            for (int i = startIndex; i <= endIndex; i++)
            {
                ans[i - startIndex] = arr[i];
            }

            return ans;
        }

        /// <summary>
        /// appedns two arrays to one.
        /// </summary>
        /// <param name="arr1"></param>
        /// <param name="arr2"></param>
        /// <returns></returns>
        public static int[] appendArrays(int[] arr1, int[] arr2)
        {
            int[] ans = new int[arr1.Length + arr2.Length];

            for (int i = 0; i < arr1.Length || i < arr2.Length; i++)
            {
                if (i < arr1.Length)
                {
                    ans[i] = arr1[i];
                }
                if (i < arr2.Length)
                {
                    ans[i + arr1.Length] = arr2[i];
                }
            }

            return ans;
        }

        /// <summary>
        /// get hash and return is suitable nGram string
        /// </summary>
        /// <param name="hash"></param>
        /// <returns></returns>
        public string GetStringByHash(int hash)
        {
            string ans;
            if (this.m_allHashedNGrams.ContainsKey(hash))
                ans =  this.m_allNGramsSentences[hash];
            else
                ans = "";
            return ans;
        }

        /// <summary>
        /// returns the max occurences of a nGram in the text. used for calculate tf.
        /// </summary>
        /// <param name="n"></param>
        /// <param name="k"></param>
        /// <returns></returns>
        public int GetMaxOccurencesInDoc(int n, int k)
        {
            return m_allHashedNGrams.Values.Max();
        }

    } // class
        

}